﻿using System.Collections.Generic;
using System.Runtime.InteropServices;
using OpenCompiler.Scanning.Scanners;

namespace OpenCompiler.Scanning
{
	public unsafe class Scanner
	{
		private readonly TokenScanner[] _tokenScanners = new TokenScanner[]
		                                         	{
														new FormatterScanner(),
														new CompilerDirectivesScanner(),
														new CommentScanner(),
														new NumericLiteralScanner(),
														new KeywordOrIdentifierScanner(),
														new StringLiteralScanner(),
														new CoalesceScanner(),
														new OperatorScanner(),
														new EqualityScanner(),
														new AssignmentScanner(),
														new ArithmeticScanner()
		                                         	};



		public Scanner(string fileName)
		{
			this.FileName = fileName;
		}


		public string FileName { get; private set; }


		public List<Token> Tokenize(string input)
		{
			var tokens = new List<Token>();
			var bstr = Marshal.StringToBSTR(input);
			var stringPointer = (char*)bstr;
			var originalStringPointer = (char*)bstr;
			var currentLinePointer = originalStringPointer;

			var lineNumber = 1;

			var file = new SourceFileInfo();
			file.Filename = this.FileName;
			var currentLine = new LineInfo { File = file,Line = lineNumber };
			var tokenColumn = 1;

			for (; ; )
			{
				var remainingCharacters = input.Length-(int)(stringPointer - originalStringPointer);
				if (remainingCharacters <= 0)
				{
					break;
				}

				var currentCharacter = stringPointer[0];
				// Whitespace
				if (TokenHelper.IsWhiteSpace(currentCharacter))
				{
					stringPointer += TokenHelper.GetWhiteSpacePhysicalLength(currentCharacter);
					tokenColumn += TokenHelper.GetWhiteSpaceVirtualLength(currentCharacter);
				}
				else
				{
					var foundScanner = false;
					var newLine = false;
					for (var scannerIndex = 0; scannerIndex < this._tokenScanners.Length; scannerIndex++)
					{
						var scanner = this._tokenScanners[scannerIndex];
						if (scanner.IsToken(stringPointer, remainingCharacters))
						{
							var stringPointerBefore = stringPointer;
							var token = scanner.GetToken(ref stringPointer, remainingCharacters);
							var tokenLength = (int)(stringPointer - stringPointerBefore);
							if (!scanner.CanTokenBeSkipped)
							{
								var tokenToAdd = new Token {Location = currentLine, ActualToken = token, Column = tokenColumn};
								tokenColumn += tokenLength;
								tokens.Add(tokenToAdd);
							}
							if (scanner.CanSpanMultipleLines)
							{
								var lines = TokenHelper.CountCharacterOccurence(stringPointerBefore, tokenLength, '\n');
								lineNumber += lines;
								newLine = true;
							}
							else
							{
								if (token is Formatting && ((Formatting)token) == Formatting.Newline)
								{
									lineNumber++;
									newLine = true;
								}
							}
							foundScanner = true;
							break;
						}
					}
					if (!foundScanner)
					{
						stringPointer++;
					}
					if( newLine )
					{
						currentLine = new LineInfo { File = file, Line = lineNumber };
						currentLinePointer = stringPointer;
						tokenColumn = 1;
					}
				}
			}
			var endOfFileToken = new Token {Location = currentLine, ActualToken=Formatting.EndOfFile};
			tokens.Add(endOfFileToken);

			Marshal.FreeBSTR(bstr);

			return tokens;
		}
	}
}
